Public
Edited
Dec 30, 2022
5 stars
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
viz = {
mutable type = 0
const svg = d3.create('svg').attr('class', 'svg-vis').attr("viewBox", [0, 0, width_c, height])
const south_asian_countries = ['Indonesia', 'Philippines', 'Thailand', 'Vietnam', 'Malaysia']

const projection = d3.geoEquirectangular().fitSize([width_c, height], mapGeoJson)
const wrapper = svg.append('g')

let geoGenerator = d3.geoPath().projection(projection)

let regions = wrapper.selectAll('region').data(mapGeoJson.features)
let areas = regions.enter().append('path').attr('class', 'areas').attr('d', d=>geoGenerator(d)).attr('fill','#969696').attr('opacity', 0)

const nodes = wrapper.selectAll('circle').data(nodes_data).join('circle').attr('r', node_radius).attr('fill', 'gray').attr('stroke', 'whitesmoke').attr('stroke-width', 0.2)

const river_group = wrapper.append('g').attr('opacity', 0)
const xAxisGroup = river_group.append('g').attr('transform', `translate(${0}, ${height - (height/3)})`).call(xAxisAllRivers).selectAll("text").attr("dy", "-8px").attr("dx", "-5px").style("text-anchor", "end").attr("transform", "rotate(-90)")
const riverbar = river_group.append('g').attr('transform', `translate(${0}, ${(0)})`)
.selectAll('rect').data(all_rivers_plastic1).join('rect')
.attr('y', v => y_all_rivers(v["Share of global plastics emitted to ocean"]))
.attr('x', v => x_all_rivers(v["Entity"]))
.attr('height', v => y_all_rivers(0) - y_all_rivers(v["Share of global plastics emitted to ocean"]))
.attr('width', x_all_rivers.bandwidth()-3)
.attr('fill', v => v.ph ? "red" : "gray")
const yAxisGroup = river_group.append('g')
.attr('transform', `translate(${margin.right}, ${0})`).call(yAxisAllRivers)
.call(g => g.append('text')
.attr('opacity', 0.8)
.attr('x', 10)
.attr('y', margin.top)
.attr('fill','gray')
.attr('font-weight', 'bold')
.attr('font-size', "0.50rem")
.attr('text-anchor', 'start')
.text('% of Plastic Emission Contribution'))
// // init simulation
const simulation = d3.forceSimulation();
simulation.nodes(nodes_data);
simulation.force('charge', d3.forceManyBody().strength(0))
.force('collision', d3.forceCollide().radius(d => node_radius+0.5).iterations(10)).stop()


const annotate_node = wrapper.append("g")
.attr('transform', `translate(${(width_c/4-5)}, ${(height/3) - 2})`)
annotate_node.append('path')
.attr('d', generateArrow( -200, 0, 0, 0, 5, 1, 1))
.style('fill', 'none')
.style('stroke', 'black');

annotate_node.append('text').text('Each Dot = 1000 Tones of Trash').attr('x', -200).attr('dy', -9).attr('class', 'annotate_node')
const countries_bar = wrapper.append("g")
.attr('transform', `translate(${margin.left}, ${height - (height/4)})`).call(yAxisCountries)
.selectAll("text")
.style("text-anchor", "end").attr('opacity', 1).attr("dy", "-16px").attr("dx", "-15px")
.attr("transform", "rotate(-90)");


const scatter_group = wrapper.append('g')
const scatter_X_axis =scatter_group.append('g').attr('opacity', 0.8)
.attr('transform', `translate(0, ${height/2})`)
.call(scatterXAxis)
.call(g => g.append('text')
.attr('opacity', 0.8)
.attr('x', width_c - 200)
.attr('y', 20)
.attr('fill','gray')
.attr('font-weight', 'bold')
.attr('font-size', "0.61rem")
.attr('text-anchor', 'start')
.text('→ GDP per Capita (Purchasing Power)'))


scatter_group.append("g").attr("transform", `translate(${width_c - (width_c/3)}, ${height/3})`)
.call(circleLegend);
const scatter_Y_axis = scatter_group.append('g').attr('opacity', 0.8)
.attr('transform', `translate(${margin.left}, 0)`)
.call(scatterYAxis)
.call(g => g.append('text')
.attr('opacity', 0.8)
.attr('x', 10)
.attr('y', 20)
.attr('fill','gray')
.attr('font-weight', 'bold')
.attr('font-size', "0.61rem")
.attr('text-anchor', 'start')
.text('Total Plastic Emission ↑'))


const circleScatter = wrapper.append("g")
.attr("stroke", "whitesmoke")
.attr("stroke-opacity", 1.2)
.selectAll("circle")
.data(plastic_dataset)
.enter().append("circle")
.attr("cx", d => xScaleScatter(d.GDP_per_capita))
.attr("cy", d => yScaleScatter(d.pw_total))
.attr('fill', d => { if (south_asian_countries.includes(d.Entity)) {
return 'red'
} else {
return 'gray'
}})
.attr('opacity', 0.5)
.on("mousemove", (event, val) => mouseover_animation(val,event)).on("mouseout", (d) => {
tooltip.style("visibility",'hidden')
})
const mouseover_animation = (val, event) => {
tooltip.style("visibility", "visible").style("top", (event.y-10)+"px").style("left",(event.x+10)+"px")
.html("<div><p>Country: " + val.Entity + "</p>"
+"<p>Plastic Waste Total: " + (val.pw_total/1000).toFixed(2) + "k</p>"
+"</div>")
}

const scatterText = scatter_group.append("g")
.selectAll("text")
.data(plastic_dataset)
.enter().append("text")
.attr("x",d => xScaleScatter(d.GDP_per_capita))
.attr("y", d => yScaleScatter(d.pw_total))
.attr("dy", d => (sizeScale(d.mismanaged_pw_capita)+10))
.style("font-size", d => Math.max(10,sizeScale(d.mismanaged_pw_capita) * 0.4))
.style("text-anchor", "middle")
.attr('fill', d => { if (south_asian_countries.includes(d.Entity)) {
return "red"
} else {
return 'gray'
}})
.text(function(d) { return d.Entity; });

return Object.assign (
html`
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Oswald">
<div class="container">
<div class="controls">
<p>Visualization Controls</p>
<div>
<Button class="button-style" id="prev-slide">Previous Slide</Button>
<Button class="button-style" id="next-slide">Next Slide</Button>
<Button class="button-style" id="reset">Reset</Button>
</div>
</div>
<div class='annotation-section' id="annotation-sec">
${viz_descriptions}
</div>
${svg.node()}
</div>`,
{
morph(type) {
d3.select('.annotation-section').style('max-height', width*0.6 + "px")
d3.selectAll('.desc-section').style('height', width*0.6 + "px")
if (type == 0) {

nodes.attr('transform', function(d) {
return `translate(${width_c/2},${height/2 })`})
// Nodes
simulation.stop()
// nodes.transition()
// .duration(800)
// .attr('r', node_radius)
// .attr('opacity', 0)
// .attr('transform', d => `translate(${Math.random() * width_c },${0})`).attr('fill', 'gray')
// Nodes
simulation.force('charge', d3.forceManyBody().strength(-5))
.force('collision', d3.forceCollide().radius(d => Math.ceil(Math.random()*10, 3)).strength(1))
.force('center', d3.forceCenter(width_c/2, height/2).strength(1))
.force('y', d3.forceY().y(height/2).strength(1))
.force('x', d3.forceX().x(width_c/2).strength(1))

simulation.on('tick', () => {
nodes.transition().duration(1500).ease(d3.easeLinear).attr('transform', function(d) {
return `translate(${d.x},${ d.y })`}).attr("fill", 'gray').attr('opacity', 0.6).attr('r', d => Math.ceil(Math.random()*10, 3))})
simulation.velocityDecay(0.3).alpha(1).restart()
// hide
annotate_node.transition().attr('opacity', 0)
scatter_group.transition().attr('opacity', 0)
circleScatter.transition().duration(800).attr("r",0);
countries_bar.transition().attr('opacity', 0)
areas.transition().attr('opacity', 0)
river_group.transition().attr('opacity', 0)
} else if (type == 1) {
// Nodes
simulation.force('charge', d3.forceManyBody().strength(0))
.force('collision', d3.forceCollide().radius(d => node_radius+0.5).iterations(10))
.force('charge', d3.forceManyBody().strength(0)).force('center', d3.forceCenter(width_c/2, height/2).strength(0)).stop()
nodes.transition().duration(1500)
.attr('opacity', 1)
.attr('transform', d => `translate(${d.x_grid},${d.y_grid})`).attr('fill', 'gray').attr("r",node_radius);

annotate_node.transition().attr('opacity',1)
// hide
scatter_group.transition().attr('opacity', 0)
countries_bar.transition().attr('opacity', 0)
areas.transition().attr('opacity', 0)
circleScatter.transition().duration(800).attr("r",0);
} else if (type == 2) {
simulation.stop()
// show
areas.transition().duration(400).attr('opacity', 0.4)

// Nodes
simulation .force('charge', d3.forceManyBody().strength(0))
.force('collision', d3.forceCollide().radius(d => node_radius+0.5).iterations(10))
.force('y', d3.forceY().y(d => d.y_map))
.force('x', d3.forceX().x(d => d.x_map))
simulation.on('tick', () => {
nodes.transition().duration(800).ease(d3.easeLinear).attr('transform', function(d) {
return `translate(${d.x},${ d.y})`}).attr('fill', 'gray')
});

simulation.alpha(1).alphaDecay(0.05).velocityDecay(0.25).restart()

// hide
annotate_node.transition().attr('opacity',0)
scatter_group.transition().attr('opacity', 0)
countries_bar.transition().attr('opacity', 0)
circleScatter.transition().duration(800).attr("r",0);
} else if (type == 3) {
simulation.stop()
// show
areas.transition().duration(400).attr('opacity', 0.4)

// Nodes
simulation.force('y', d3.forceY().y(d => d.y_map)).force('x', d3.forceX().x(d => d.x_map))
simulation.on('tick', () => {
nodes.transition().duration(800).ease(d3.easeLinear).attr('transform', function(d) {
return `translate(${d.x},${ d.y})`}).attr('fill', d => { if (south_asian_countries.includes(d.Entity)) {
return 'red'
} else {
return 'gray'
}})})
simulation.alpha(1).alphaDecay(0.05).velocityDecay(0.25).restart()

// hide
river_group.transition().attr('opacity', 0)
scatter_group.transition().attr('opacity', 0)
countries_bar.transition().attr('opacity', 0)
circleScatter.transition().duration(800).attr("r",0);
} else if (type == 4) {
simulation.stop()

// show
countries_bar.transition().attr('opacity', 1)
// Nodes
simulation.force('y', d3.forceY().y(d => x_count(d.count)).strength(4))
.force('x', d3.forceX().x(d => y_countries(d.Entity)).strength(1))
.force('collision', d3.forceCollide().radius(d => node_radius+0.5).iterations(10))
simulation.restart()
simulation.on('tick', () => {
nodes.transition().duration(800).ease(d3.easeLinear).attr('transform', function(d) {
return `translate(${d.x + margin.left},${ d.y })`}).attr('fill', d => { if (south_asian_countries.includes(d.Entity)) {
return 'red'
} else {
return 'gray'
}}).attr('opacity', 1)})


simulation.alpha(1).alphaDecay(0.1).velocityDecay(0.36).restart()
// hide
river_group.transition().attr('opacity', 0)
scatter_group.transition().attr('opacity', 0)
areas.transition().attr('opacity', 0)
circleScatter.transition().duration(800).attr("r",0);
} else if (type == 5) {
simulation.stop()

// show
countries_bar.transition().attr('opacity', 1)
// Nodes
simulation.force('y', d3.forceY().y(d => x_count(d.count)).strength(4))
.force('x', d3.forceX().x(d => y_countries(d.Entity)).strength(1))
.force('collision', d3.forceCollide().radius(d => node_radius+0.5).iterations(10))
simulation.restart()
simulation.on('tick', () => {
nodes.transition().duration(800).ease(d3.easeLinear).attr('transform', function(d) {
return `translate(${d.x + margin.left},${ d.y })`}).attr('fill', d => { if (d.Entity == 'Philippines') {
return 'red'
} else {
return 'gray'
}}).attr('opacity', 1)})


simulation.alpha(1).alphaDecay(0.1).velocityDecay(0.36).restart()
// hide
river_group.transition().attr('opacity', 0)
scatter_group.transition().attr('opacity', 0)
areas.transition().attr('opacity', 0)
circleScatter.transition().duration(800).attr("r",0);
} else if (type == 6) {
simulation.stop()
// show
scatter_group.transition().attr('opacity', 1)
circleScatter.transition().duration(5000).attr("r", d => sizeScale(d.mismanaged_pw_capita));
// Nodes
nodes.transition().delay((d, i) => i * (Math.random() * 5)).duration(Math.random() * 500).ease(d3.easeLinear).attr('transform', function(d) {
return `translate(${xScaleScatter(d.GDP_per_capita)},${yScaleScatter(d.pw_total)})`}).transition().attr('opacity', 0)

// hide
river_group.transition().attr('opacity', 0)
areas.transition().attr('opacity', 0)
countries_bar.transition().attr('opacity', 0)

} else if (type == 7) {
simulation.stop()
nodes.transition()
.duration(800)
.attr('r', node_radius)
.attr('opacity', 0)
.attr('transform', d => `translate(${Math.random() * width_c},${0})`).attr('fill', "gray")

river_group.transition().attr('opacity', 1)
// hide
scatter_group.transition().attr('opacity', 0)
countries_bar.transition().attr('opacity', 0)
areas.transition().attr('opacity', 0)
circleScatter.transition().duration(800).attr("r",0);

} else if (type == 8) {

// Nodes
simulation.stop()
nodes.transition().duration(800)
.attr('opacity', 1)
.attr('transform', d => `translate(${d.x_grid},${d.y_grid})`).attr('fill', "red")
.transition().delay((d, i) => i * (Math.random() * 30)).duration(Math.random() * 1500)
.attr('transform', d => `translate(${d.x_grid},${width_c})`).attr('opacity', 0)


// hide
river_group.transition().attr('opacity', 0)
scatter_X_axis.transition().attr('opacity', 0)
scatter_Y_axis.transition().attr('opacity', 0)
countries_bar.transition().attr('opacity', 0)
scatterText.transition().attr('opacity', 0)
areas.transition().attr('opacity', 0)
// hide
scatter_group.transition().attr('opacity', 0)
circleScatter.transition().duration(800).attr("r",0);
}
}
})
}
Insert cell
Insert cell
fpsMeter()
Insert cell
style = html`
<style>
.html {
background-color:whitesmoke;
}
.container {
position: relative;
}

.annotate_node {
font-size:13px;
font-weight:bold;
}

.controls {
display:flex;
flex-direction: row;
justify-content:start;
align-items:center;
position: sticky;
height:2rem;
color:white;
z-index:999;
}

.annotation-section {
width:100%;
position: absolute;
overflow: hidden;
z-index: 999
-ms-overflow-style: none; /* Internet Explorer 10+ */
scrollbar-width_c: none; /* Firefox */
}
h1 {
font-size:3vw;
}
h2 {
font-size: 2vw
}
p {
font-size:0.8rem;
}
.annotation-section::-webkit-scrollbar {
display: none;
}

.desc-section {
display:flex;
flex-direction:column;
justify-content:space-between;
align-items:center;
text-align:center;
}

.svg-vis {
position:relative
}

.tooltip {
position: absolute;
font-size: 0.67rem;
display: flex;
flex-direction: column;
justify-content: center;
z-index: 10;
background: gray;
border-radius: 2px;
color:#fff;
visibility:hidden;
padding:5px;
}

.desc-header {
display: flex;
flex-direction: column;
justify-content: center;
align-items:center;
margin-top:3%;
z-index: 10;
width: 60%;
}
.desc-box {
position:relative;
font-size: 1rem;
width:80%;
display: flex;
flex-direction: column;
justify-content: center;
align-items:center;
z-index: 10;
background: gray;
border-radius: 2px;
color:#fff;
opacity:80%;
margin-bottom:3%;
}
</style>
`
Insert cell
Insert cell
Insert cell
viz.morph(type)
Insert cell
projection = d3.geoEquirectangular().fitSize([width_c, height], mapGeoJson)
Insert cell
tooltip = d3
.select("body")
.append("div")
.attr('class', 'tooltip')
Insert cell
// button controls
{
d3.select('#next-slide').on('click', () => {
if (type <= 7) {
mutable type += 1
var objDiv = document.getElementById('annotation-sec')
objDiv.scrollTo({top: objDiv.scrollTop + width*0.6, behavior:'smooth'})
}
})
d3.select('#prev-slide').on('click', () => {
if (type >= 0) {
mutable type -= 1
var objDiv = document.getElementById('annotation-sec')

objDiv.scrollTo({top: objDiv.scrollTop - width*0.6, behavior:'smooth'})
}
})
d3.select('#reset').on('click', () => {
mutable type = 0
var objDiv = document.getElementById('annotation-sec')
objDiv.scrollTo({top:0, behavior:'smooth'})
})
}
Insert cell
Insert cell
Insert cell
Insert cell
scatterXAxis = g => g.call(d3.axisTop(xScaleScatter))

Insert cell
scatterYAxis = g => g.call(d3.axisLeft(yScaleScatter))
Insert cell
Insert cell
Insert cell
Insert cell
xScaleScatter = d3.scaleSqrt().domain([1, 80000]).range([margin.left, width_c - margin.right])

Insert cell
yScaleScatter = d3.scaleLog().domain([31300, 70079741]).range([height-(height/5), 10])

Insert cell
xAxisAllRivers = g => g.call(d3.axisBottom(x_all_rivers)).call(g => g.select('.domain').remove()).call(g => g.selectAll('line').remove())

Insert cell
yAxisAllRivers = g => g.call(d3.axisLeft(y_all_rivers).tickFormat(d => `${d} %`).ticks(5)).call(g => g.select('.domain').remove())

Insert cell
x_all_rivers = d3.scaleBand().domain(all_rivers_plastic1.map(v => v.Entity)).range([margin.left, width_c-margin.right])

Insert cell
y_all_rivers = d3.scaleLinear().domain([0, 7]).range([height-(height/3), margin.top])
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
mapGeoJson = FileAttachment("world.geojson (2).json").json()
Insert cell
phl_regions = FileAttachment("phl_regions2.geojson").json()
Insert cell
all_rivers_plastic = FileAttachment("plastics-top-rivers (1).csv").csv()
Insert cell
all_rivers_plastic1 = {
let temp_arr = all_rivers_plastic.sort((a, b) => b["Share of global plastics emitted to ocean"] - a["Share of global plastics emitted to ocean"]).slice(0,50)

temp_arr = temp_arr.map(v => {
if (v.Entity.includes('Philippines')) {
return {...v, 'ph':1}
} else {
return {...v, 'ph': 0}
}
})
return temp_arr
}
Insert cell
nodes_data = {
// convert raw data to nodes
const nodes = []
const count_dict = {}

plastic_dataset.forEach(v => {
for (let i = 0; i < Math.floor(v.mismanaged_pw_total/1000); i++) {
count_dict[v.Entity] = (count_dict[v.Entity] || 0) + 1;
let projected_latlong = projection([parseFloat(v.long), parseFloat(v.lat)])
nodes.push({...v, count: count_dict[v.Entity]})
}
})

// calculate grid positions
const cols = 50
const rows = 19
let box_size = width_c / 2
let box_height = height / 2
// add a grid scale
const calc_grid_pos = (i, nc, nr) => {
const cs = (box_height) / nc
const rs = (box_size) / nr
return {y: (Math.floor(i/nr) * cs) + ((height/4)),
x: rs * (i % nr) + (((width_c) / 2) - box_size/2)}
}

return nodes.sort((a,b) => b.mismanaged_pw_total - a.mismanaged_pw_total).map((v, i) => {
const result = calc_grid_pos(i, rows, cols)
let t = projection([parseFloat(v.long), parseFloat(v.lat)])
return (
{...v, x_grid: result.x, y_grid: result.y, x_map:t[0], y_map:t[1]}
)
})
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
plastic_dataset@2.csv
Type Table, then Shift-Enter. Ctrl-space for more options.

Insert cell

One platform to build and deploy the best data apps

Experiment and prototype by building visualizations in live JavaScript notebooks. Collaborate with your team and decide which concepts to build out.
Use Observable Framework to build data apps locally. Use data loaders to build in any language or library, including Python, SQL, and R.
Seamlessly deploy to Observable. Test before you ship, use automatic deploy-on-commit, and ensure your projects are always up-to-date.
Learn more